//
//  DictionaryUtils.swift
//  novelReader
//
//  Created by Ink on 2019/11/29.
//  Copyright © 2019 ChineseAll. All rights reserved.
//
import Foundation
import AnyCodable

public extension Dictionary {
    subscript(_ key: Key?) -> Value? {
        get {
            guard let key = key else { return nil }
            return self[key]
        }
        set {
            guard let key = key else { return }
            self[key] = newValue
        }
    }
    
    func value(forKeys keys: Key?...) -> Value? {
        for key in keys {
            if let key = key, let value = self[key] {
                return value
            }
        }
        return nil
    }
}

extension Dictionary {
    
    /// 字典拼接成字符串
    public func transitionString(separator: String = "&", urlEncode: Bool = false) -> String? {
        components()?
            .map { "\($0)=\(urlEncode ? $1.fixedArabicURL : $1)" }
            .joined(separator: separator)
    }
    
    public func components() -> [(String, String)]? {
        guard let dataDic = self as? [String: Any?], !dataDic.isEmpty else {
            return nil
        }
        return dataDic
            .compactMap { key, value in
                if let unwrapped = value {
                    return (key, "\(unwrapped)")
                } else {
                    return nil
                }
            }
            .sorted { $0.0 < $1.0 }
    }
    
    @inlinable public mutating func merge(
        _ other: [Key: Value]?,
        uniquingKeysWith combine: ((Value, Value) -> Value) = { $1 }
    ) {
        guard let other = other else { return }
        self.merge(other, uniquingKeysWith: combine)
    }
    
    @inlinable public func merging<S>(
        _ other: S?,
        uniquingKeysWith combine: ((Value, Value) -> Value) = { $1 }
    ) -> [Key: Value] where S: Sequence, S.Element == (Key, Value) {
        guard let other = other else {
            return self // If `other` is nil, return the current dictionary.
        }
        return self.merging(other, uniquingKeysWith: combine)
    }
    
    @inlinable public func merging(
        _ other: [Key : Value]?,
        uniquingKeysWith combine: ((Value, Value) -> Value) = { $1 }
    ) -> [Key: Value] {
        guard let other = other else {
            return self // If `other` is nil, return the current dictionary.
        }
        return self.merging(other, uniquingKeysWith: combine)
    }
}

public extension Dictionary where Key == String {

    func toJsonString(prettyPrinted: Bool = false) -> String? {
        let cleaned = NSObject.recursivelyUnwrap(self)
        guard JSONSerialization.isValidJSONObject(cleaned) else { return nil }

        let options: JSONSerialization.WritingOptions = prettyPrinted ? [.prettyPrinted, .sortedKeys] : [.sortedKeys]
        guard let data = try? JSONSerialization.data(withJSONObject: cleaned, options: options) else { return nil }

        return String(data: data, encoding: .utf8)
    }
}

public extension NSObject {
    static func recursivelyUnwrap(_ value: Any) -> Any {
        switch value {
        case let anyCodable as AnyCodable:
            return recursivelyUnwrap(anyCodable.value ?? NSNull())

        case let url as URL:
            return url.absoluteString
            
        case let nsurl as NSURL:
            return nsurl.absoluteString ?? ""

        case let dict as [String: Any]:
            var newDict: [String: Any] = [:]
            for (k, v) in dict {
                let unwrapped = recursivelyUnwrap(v)
                newDict[k] = unwrapped
            }
            return newDict

        case let dict as [String: AnyCodable]:
            var newDict: [String: Any] = [:]
            for (k, v) in dict {
                let unwrapped = recursivelyUnwrap(v.value ?? NSNull())
                newDict[k] = unwrapped
            }
            return newDict

        case let array as [Any]:
            return array.map { recursivelyUnwrap($0) }

        case let array as [AnyCodable]:
            return array.map { recursivelyUnwrap($0.value ?? NSNull()) }

        case Optional<Any>.none:
            return NSNull()

        case let number as NSNumber:
            return number

        case let bool as Bool:
            return bool

        case let string as String:
            return string

        default:
            return String(describing: value) // 兜底转换为 String 避免 JSONSerialization 崩溃
        }
    }
}
@dynamicMemberLookup
struct DictionaryChain<T> {
    let value: [String: Any]
    
    subscript(dynamicMember key: String) -> DictionaryChain<Any> {
        if let dictionary = value[key] as? [String: Any] {
            return DictionaryChain<Any>(value: dictionary)
        } else {
            return DictionaryChain<Any>(value: [:])
        }
    }
    
    subscript(dynamicMember key: String) -> T? {
        return value[key] as? T
    }
}

public extension Dictionary where Key == String, Value == Any {
    func value(forKeyPath keyPath: String) -> Any? {
        let keys = keyPath.split(separator: ".").map(String.init)
        var current: Any? = self
        for key in keys {
            if let dict = current as? [String: Any] {
                current = dict[key]
            } else {
                return nil
            }
        }
        return current
    }
    
    mutating func setNestedValue(_ value: Any?, forKeyPath keyPath: String) {
        var keys = keyPath.split(separator: ".").map(String.init)
        guard !keys.isEmpty else { return }
        let firstKey = keys.removeFirst()
        
        if keys.isEmpty {
            self[firstKey] = value
        } else {
            var nested = self[firstKey] as? [String: Any] ?? [:]
            nested.setNestedValue(value, forKeyPath: keys.joined(separator: "."))
            self[firstKey] = nested
        }
    }
}
